package fun.qu_an.minecraft.mini_games_helper.client.handle;

import com.llamalad7.mixinextras.lib.apache.commons.tuple.Pair;
import com.mojang.serialization.Codec;
import fun.qu_an.minecraft.mini_games_helper.client.config.MiniGamesHelperClientConfig;
import fun.qu_an.minecraft.mini_games_helper.client.hook.SimpleOptionsHook;
import fun.qu_an.minecraft.mini_games_helper.config.MiniGamesHelperConfig;
import fun.qu_an.minecraft.mini_games_helper.network.MGHConfigS2CPacket;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.client.gui.widget.ButtonWidget;
import net.minecraft.client.gui.widget.ClickableWidget;
import net.minecraft.client.option.GameOptions;
import net.minecraft.client.option.SimpleOption;
import net.minecraft.client.toast.SystemToast;
import net.minecraft.world.GameMode;

import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;

@Environment(EnvType.CLIENT)
public class ForceOptionHelperVanilla {
	public static final SystemToast.Type OPTION_RECEIVE_FAILURE = new SystemToast.Type();
	private static final Map<SimpleOption<?>, SimpleOption<?>> creative = new IdentityHashMap<>();
	private static final Map<SimpleOption<?>, SimpleOption<?>> survival = new IdentityHashMap<>();
	private static final Map<SimpleOption<?>, SimpleOption<?>> adventure = new IdentityHashMap<>();
	private static final Map<SimpleOption<?>, SimpleOption<?>> spectator = new IdentityHashMap<>();
	private static Map<SimpleOption<?>, SimpleOption<?>> currentMapPtr = Map.of();
	private static final Map<String, Pair<SimpleOption<?>, Class<?>>> vanillaOptions = new HashMap<>();

	public static void receive(MGHConfigS2CPacket packet) {
		MiniGamesHelperConfig config = packet.config();
		receive0(config.getSurvival(), survival);
		receive0(config.getCreative(), creative);
		receive0(config.getAdventure(), adventure);
		receive0(config.getSpectator(), spectator);
		// TODO fix thread safe
	}

	public static void revoke() {
		currentMapPtr = Map.of();
	}

	public static void apply(GameMode gameMode) {
		currentMapPtr = switch (gameMode) {
			case SURVIVAL -> survival;
			case CREATIVE -> creative;
			case ADVENTURE -> adventure;
			case SPECTATOR -> spectator;
		};
	}

	public static <T> SimpleOption<T> getForced(SimpleOption<T> option) {
		//noinspection unchecked
		return (SimpleOption<T>) currentMapPtr.get(option);
	}

	public static <T> SimpleOption<T> getForced(String key) {
		SimpleOption<Object> option = getVanilla(key);
		if (option == null) {
			return null;
		}
		//noinspection unchecked
		return (SimpleOption<T>) currentMapPtr.get(option);
	}

	public static <T> SimpleOption<T> getVanilla(String key) {
		Pair<SimpleOption<?>, Class<?>> pair = vanillaOptions.get(key);
		if (pair == null) {
			return null;
		}
		//noinspection unchecked
		return (SimpleOption<T>) pair.getLeft();
	}

	public static void putVanilla(String key, Pair<SimpleOption<?>, Class<?>> of) {
		vanillaOptions.put(key, of);
	}

	public static <T> SimpleOption<T> get(String key) {
		SimpleOption<T> vanilla = getVanilla(key);
		SimpleOption<T> forced = getForced(vanilla);
		return forced == null ? vanilla : forced;
	}

	private static void receive0(MiniGamesHelperConfig.Group group, Map<SimpleOption<?>, SimpleOption<?>> map) {
		for (MiniGamesHelperConfig.Option option : group.getOptions()) {
			String key = option.getKey();
			Pair<SimpleOption<?>, Class<?>> pair = vanillaOptions.get(key);
			if (pair == null) {
				System.err.println("key \"" + key + "\" not found, skipped!");
				continue;
			}
			SimpleOption<?> vanilla = pair.getLeft();
			//noinspection unchecked,rawtypes
			map.put(vanilla, createLocked((SimpleOption) vanilla, cast(option.getValue(), pair.getRight())));
		}
	}

	@SuppressWarnings("unchecked")
	private static <T> T cast(String value, Class<T> cls) {
		if (Integer.class.isAssignableFrom(cls)) {
			return (T) Integer.valueOf(value);
		}
		if (Double.class.isAssignableFrom(cls)) {
			return (T) Double.valueOf(value);
		}
		if (Boolean.class.isAssignableFrom(cls)) {
			return (T) Boolean.valueOf(value);
		}
//		if (String.class.isAssignableFrom(cls)) {
//			return (T) value;
//		}
		throw new UnsupportedOperationException("unsupported type " + cls.getName());
	}

	private static <T> SimpleOption<T> createLocked(SimpleOption<T> vanilla, T value) {
		//noinspection unchecked
		SimpleOption<T> option = new SimpleOption<>(vanilla.text.getLiteralString(), vanilla.tooltipFactory, ((SimpleOptionsHook<T>) (Object) vanilla).mgh$getValueTextGetter(), getLockedCallback(value), vanilla.defaultValue, v -> {
		});
		option.setValue(value);
		return option;
	}

	@SuppressWarnings("unchecked")
	private static <T> LockedOptionCallbacks<T> getLockedCallback(T value) {
		Class<?> type = value.getClass();
		if (Integer.class.isAssignableFrom(type)) {
			return () -> (Codec<T>) Codec.INT;
		}
		if (Double.class.isAssignableFrom(type)) {
			return () -> (Codec<T>) Codec.DOUBLE;
		}
		if (Boolean.class.isAssignableFrom(type)) {
			return () -> (Codec<T>) Codec.BOOL;
		}
		throw new UnsupportedOperationException("unsupported type " + type.getName());
	}

	public static boolean isDisableRecipeBook(GameMode gameMode) {
		return MiniGamesHelperClientConfig.get().isDisableRecipeBook(gameMode);
	}

	@FunctionalInterface
	private interface LockedOptionCallbacks<T> extends SimpleOption.Callbacks<T> {
		@Override
		default Function<SimpleOption<T>, ClickableWidget> getWidgetCreator(SimpleOption.TooltipFactory<T> tooltipFactory, GameOptions gameOptions, int x, int y, int width, Consumer<T> changeCallback) {
			return option -> {
				ButtonWidget widget = ButtonWidget.builder(option.textGetter.apply(option.getValue()), a -> {
				}).dimensions(x, y, width, 20).build();
				widget.active = false;
				return widget;
			};
		}

		@Override
		default Optional<T> validate(T value) {
			return Optional.of(value);
		}

		@Override
		Codec<T> codec();
	}
}
